package br.edu.ufcg.mdaveritas.split;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import mdaveritas.parser.input.PTNetParser;

import org.eclipse.m2m.atl.drivers.emf4atl.ASMEMFModel;
import org.eclipse.m2m.atl.engine.AtlEMFModelHandler;
import org.eclipse.m2m.atl.engine.AtlLauncher;
import org.eclipse.m2m.atl.engine.AtlModelHandler;

public class SplitExecutor {

	private AtlEMFModelHandler modelHandler;
	private ASMEMFModel pnMetamodel;
	private ASMEMFModel parMetamodel;

	private long start = 0;
	private long end = 0;
	private String tempPath;
	private File temp;
	private String PETRI_NET_METAMODEL;
	private String PARAMETER_METAMODEL;
	private String NET_WCS_FILE;
	private String SUBNETS_FILE;
	private String DEL_CUT_SET_TRANSFO;
	private String CHECK_CONNECTEDNESS_TRANSFO;
	private String PARAMETER_MODEL;
	private String SPLIT_TRANSFO;
	private static final int SUBNET_LOCATION = 179;
	private static final int DEBUG_LOCATION = 219;

	private URL delCuttingSetPath;
	private URL checkConnectednessPath;
	private URL splitPath;

	public SplitExecutor() {
//		try {
//			extractResources();
//			PETRI_NET_METAMODEL = tempPath
//					+ "\\resources\\placeTransitionnew.ecore";
//			PARAMETER_METAMODEL = tempPath + "\\resources\\Parameter.ecore";
//			NET_WCS_FILE = tempPath + "\\resources\\net_wcs.petrinet";
//			SUBNETS_FILE = tempPath + "\\resources\\subnets.petrinet";
//			DEL_CUT_SET_TRANSFO = tempPath + "\\resources\\DelCuttingSet.asm";
//			CHECK_CONNECTEDNESS_TRANSFO = tempPath
//					+ "\\resources\\CheckConnectedness.asm";
//			PARAMETER_MODEL = tempPath + "\\resources\\.properties";
//			SPLIT_TRANSFO = tempPath + "\\resources\\Split.asm";
			PETRI_NET_METAMODEL = "D:\\workspace\\SplitExecutor\\resources\\placeTransition.ecore";
			PARAMETER_METAMODEL = "D:\\workspace\\SplitExecutor\\resources\\Parameter.ecore";
			NET_WCS_FILE = "D:\\workspace\\SplitExecutor\\resources\\net_wcs.ptnet";
			SUBNETS_FILE = "D:\\workspace\\SplitExecutor\\resources\\subnets.ptnet";
			DEL_CUT_SET_TRANSFO = "D:\\workspace\\SplitExecutor\\resources\\DelCuttingSet.asm";
			CHECK_CONNECTEDNESS_TRANSFO = "D:\\workspace\\SplitExecutor\\resources\\CheckConnectedness.asm";
			PARAMETER_MODEL = "D:\\workspace\\SplitExecutor\\resources\\Exemplo.properties";
			SPLIT_TRANSFO = "D:\\workspace\\SplitExecutor\\resources\\Split.asm";
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
	}

	private void createResources() throws MalformedURLException {
		modelHandler = (AtlEMFModelHandler) AtlModelHandler
				.getDefault(AtlModelHandler.AMH_EMF);
		System.out.println("Loading transformation files...");

		File delCuttingSet = new File(DEL_CUT_SET_TRANSFO);
		delCuttingSetPath = delCuttingSet.toURI().toURL();
		File checkConnectedness = new File(CHECK_CONNECTEDNESS_TRANSFO);
		checkConnectednessPath = checkConnectedness.toURI().toURL();
		File split = new File(SPLIT_TRANSFO);
		splitPath = split.toURI().toURL();
	}

	private void initMetamodel(Map<String, Object> models) {
		try {
			System.out.println("Loading PetriNet metamodel...");
			pnMetamodel = (ASMEMFModel) modelHandler.loadModel("PetriNet",
					modelHandler.getMof(), new FileInputStream(
							PETRI_NET_METAMODEL));
			parMetamodel = (ASMEMFModel) modelHandler.loadModel("Parameter",
					modelHandler.getMof(), new FileInputStream(
							PARAMETER_METAMODEL));
			models.put("PetriNet", pnMetamodel);
			models.put("Parameter", parMetamodel);
		} catch (FileNotFoundException e) {
			File temp = new File(PETRI_NET_METAMODEL);
			System.out.println("PetriNet metamodel not found. Folder: "
					+ temp.getAbsolutePath());
		}
	}

	private void deleteCuttingSet(Map<String, Object> models, String netPath,
			String netWCSPath, String parPath) throws IOException {
		System.out.println("Loading models:");
		ASMEMFModel netInputModel = (ASMEMFModel) modelHandler.loadModel("net",
				pnMetamodel, new FileInputStream(netPath));
		System.out.println("- Net model: \"" + netPath + "\" loaded.");
		ASMEMFModel parInputModel = (ASMEMFModel) modelHandler.loadModel("par",
				parMetamodel, new FileInputStream(parPath));
		ASMEMFModel netWCSOutputModel = (ASMEMFModel) modelHandler.newModel(
				"netwcs", netWCSPath, pnMetamodel);

		models.put("net", netInputModel);
		models.put("par", parInputModel);
		models.put("netwcs", netWCSOutputModel);

		System.out.println("Deleting cutting set from the main net...");
		AtlLauncher.getDefault().launch(this.delCuttingSetPath,
				Collections.EMPTY_MAP, models, Collections.EMPTY_MAP,
				Collections.EMPTY_LIST, Collections.EMPTY_MAP);

		modelHandler.saveModel(netWCSOutputModel, netWCSPath, false);
	}

	private void checkConnectedness(Map<String, Object> models, String netPath,
			String netWCSPath, String subnetsPath, String parPath)
			throws IOException {
		ASMEMFModel netInputModel = (ASMEMFModel) modelHandler.loadModel("net",
				pnMetamodel, new FileInputStream(netPath));
		ASMEMFModel netWCSInputModel = (ASMEMFModel) modelHandler.loadModel(
				"netwcs", pnMetamodel, new FileInputStream(netWCSPath));
		ASMEMFModel subnetsOutputModel = (ASMEMFModel) modelHandler.newModel(
				"subnets", subnetsPath, pnMetamodel);
		ASMEMFModel parInputModel = (ASMEMFModel) modelHandler.loadModel("par",
				parMetamodel, new FileInputStream(parPath));

		models.put("net", netInputModel);
		models.put("netwcs", netWCSInputModel);
		models.put("par", parInputModel);
		models.put("subnets", subnetsOutputModel);

		System.out
				.println("Checking connectedness of the main net after the cutting set removal...");
		AtlLauncher.getDefault().launch(this.checkConnectednessPath,
				Collections.EMPTY_MAP, models, Collections.EMPTY_MAP,
				Collections.EMPTY_LIST, Collections.EMPTY_MAP);

		modelHandler.saveModel(subnetsOutputModel, subnetsPath, false);
	}

	private void split(Map<String, Object> models, String netPath,
			String subnetsPath, String splittedPath, String parPath, String netWCSPath)
			throws IOException {
		ASMEMFModel netInputModel = (ASMEMFModel) modelHandler.loadModel("net",
				pnMetamodel, new FileInputStream(netPath));
		ASMEMFModel subnetsInputModel = (ASMEMFModel) modelHandler.loadModel(
				"subnets", pnMetamodel, new FileInputStream(subnetsPath));
		ASMEMFModel netWCSInputModel = (ASMEMFModel) modelHandler.loadModel(
				"netwcs", pnMetamodel, new FileInputStream(netWCSPath));
		ASMEMFModel parInputModel = (ASMEMFModel) modelHandler.loadModel("par",
				parMetamodel, new FileInputStream(parPath));
		ASMEMFModel splittedOutputModel = (ASMEMFModel) modelHandler.newModel(
				"splitted", splittedPath, pnMetamodel);

		models.put("net", netInputModel);
		models.put("subnets", subnetsInputModel);
		models.put("splitted", splittedOutputModel);
		models.put("netwcs", netWCSInputModel);
		models.put("par", parInputModel);

		System.out.println("Applying the Split operation rules...");
		AtlLauncher.getDefault().launch(this.splitPath, Collections.EMPTY_MAP,
				models, Collections.EMPTY_MAP, Collections.EMPTY_LIST,
				Collections.EMPTY_MAP);

		modelHandler.saveModel(splittedOutputModel, splittedPath, false);
	}

	public void exec(String netPath, String splittedPath, int subnet,
			boolean debug) {
		try {
			setSubnet(SUBNET_LOCATION, subnet, DEBUG_LOCATION, debug);
			Map<String, Object> models = new HashMap<String, Object>();
			createResources();
			initMetamodel(models);
			removeTempFile(NET_WCS_FILE);
			removeTempFile(SUBNETS_FILE);
			removeTempFile(splittedPath);
			
			long startDCS = 0;
			long endDCS = 0;
			long startCC = 0;
			long endCC = 0;
			long startSpl = 0;
			long endSpl = 0;

			start = System.currentTimeMillis();
			startDCS = System.currentTimeMillis();
			deleteCuttingSet(models, netPath, NET_WCS_FILE, PARAMETER_MODEL);
			endDCS = System.currentTimeMillis();
			startCC = System.currentTimeMillis();
			PTNetParser p = new PTNetParser();
			p.createSubnets(NET_WCS_FILE, SUBNETS_FILE);
			//checkConnectedness(models, netPath, NET_WCS_FILE, SUBNETS_FILE,
				//	PARAMETER_MODEL);
			endCC = System.currentTimeMillis();
			startSpl = System.currentTimeMillis();
			split(models, netPath, SUBNETS_FILE, splittedPath, PARAMETER_MODEL, NET_WCS_FILE);
			endSpl = System.currentTimeMillis();
			end = System.currentTimeMillis();

			dispose(models);

			System.out.println("Removing temporary files...");
//			removeTempFile(NET_WCS_FILE);
//			removeTempFile(SUBNETS_FILE);
//			removeTempFile(PETRI_NET_METAMODEL);
//			removeTempFile(PARAMETER_METAMODEL);
//			removeTempFile(DEL_CUT_SET_TRANSFO);
//			removeTempFile(CHECK_CONNECTEDNESS_TRANSFO);
//			removeTempFile(PARAMETER_MODEL);
//			removeTempFile(SPLIT_TRANSFO);
//			removeTempFile(tempPath + File.separator + "resources");
//			removeTempFile(tempPath);
			System.out.println("Time to DCS: " + (endDCS - startDCS) + " ms Time to CC: " + (endCC - startCC) + " ms Time to Spl: " + (endSpl - startSpl) + " ms");
			System.out.println("Split operation completed in " + getTimeMark()/1000
					+ "ms. Splitted net saved as: " + splittedPath);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void setSubnet(int subnetLocation, int subnet, int debugLocation,
			boolean debug) {
		System.out.println("Subnet: " + subnet);
		System.out.println("Debug: " + debug);
		try {
			File f = new File(PARAMETER_MODEL);
			RandomAccessFile raf = new RandomAccessFile(f, "rw");
			raf.seek(subnetLocation);
			raf.writeBytes(Integer.toString(subnet));
			raf.seek(debugLocation);
			raf.writeBytes(debug ? "1" : "0");
			raf.close();
		} catch (FileNotFoundException e) {
			System.out.println("Properties file \"" + PARAMETER_MODEL
					+ "\" not found. Is it in the 'resources' folder?");
		} catch (IOException e) {
			System.out
					.println("Impossible to access the transformation file \""
							+ SPLIT_TRANSFO + "\"");
		}
	}

	private void removeTempFile(String filepath) {
		File tempFile = new File(filepath);
		tempFile.delete();
	}

	private void dispose(Map<String, Object> models) {
		for (Object model : models.values())
			((ASMEMFModel) model).dispose();
	}

	public long getTimeMark() {
		return end - start;
	}

	private void extractResources() throws IOException {
		System.out.println("Extracting temporary files...");
		String home = SplitExecutor.class.getProtectionDomain().getCodeSource()
				.getLocation().toString().substring(6);
		temp = File.createTempFile("SplitExec", "");
		temp.delete();
		temp.mkdir();
		tempPath = temp.getPath();
		JarFile jar = new JarFile(home);
		Enumeration<JarEntry> enu = jar.entries();
		while (enu.hasMoreElements()) {
			JarEntry file = (JarEntry) enu.nextElement();
			File f = new File(tempPath, file.getName());
			if (file.getName().startsWith("resources")) {
				if (file.isDirectory()) {
					f.mkdir();
					continue;
				}

				InputStream is = jar.getInputStream(file);
				FileOutputStream fos = new FileOutputStream(f);
				while (is.available() > 0) {
					fos.write(is.read());
				}
				fos.close();
				is.close();
			}
		}
	}

	public static void main(String[] args) throws IOException {
		SplitExecutor t = new SplitExecutor();
		t.exec(args[0], args[1], Integer.parseInt(args[2]), Boolean
				.parseBoolean(args[3]));

	}
}
